using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Urs.Core;
using Urs.Core.Data;
using Urs.Data.Domain.Stores;
using Urs.Data.Domain.Orders;
using Urs.Data.Domain.Payments;
using Urs.Data.Domain.Shipping;
using Urs.Services.Stores;


namespace Urs.Services.Orders
{
    public partial class OrderReportService : IOrderReportService
    {
        #region Fields

        private readonly IRepository<Order> _orderRepository;
        private readonly IRepository<OrderItem> _opvRepository;
        private readonly IRepository<Goods> _goodsRepository;
        private readonly IGoodsService _goodsService;
        private readonly IRepository<GoodsCategory> _goodsCategoryRepository;

        #endregion

        #region Ctor

        public OrderReportService(IRepository<Order> orderRepository,
            IRepository<OrderItem> opvRepository,
            IRepository<Goods> goodsRepository,
            IGoodsService goodsService,
            IRepository<GoodsCategory> goodsCategoryRepository)
        {
            this._orderRepository = orderRepository;
            this._opvRepository = opvRepository;
            this._goodsRepository = goodsRepository;
            this._goodsService = goodsService;
            this._goodsCategoryRepository = goodsCategoryRepository;
        }

        #endregion

        #region Methods

        public virtual OrderAverageReportLine GetOrderAverageReportLine(
            List<int> osIds = null, List<int> psIds = null, List<int> ssIds = null,
            DateTime? startTimeUtc = null, DateTime? endTimeUtc = null,
            bool ignoreCancelledOrders = false)
        {
            var query = _orderRepository.Table;
            query = query.Where(o => !o.Deleted);
            if (ignoreCancelledOrders)
            {
                int cancelledOrderStatusId = (int)OrderStatus.Cancelled;
                query = query.Where(o => o.OrderStatusId != cancelledOrderStatusId);
            }
            if (osIds != null && osIds.Any())
                query = query.Where(o => osIds.Contains(o.OrderStatusId));
            if (psIds != null && psIds.Any())
                query = query.Where(o => psIds.Contains(o.PaymentStatusId));
            if (ssIds != null && ssIds.Any())
                query = query.Where(o => ssIds.Contains(o.ShippingStatusId));
            if (startTimeUtc.HasValue)
                query = query.Where(o => startTimeUtc.Value <= o.CreateTime);
            if (endTimeUtc.HasValue)
                query = query.Where(o => endTimeUtc.Value >= o.CreateTime);


            var item = from b in query
                       group b by new { b.Id, b.OrderTotal } into g
                       select new { count = g.Count(), ordertotal = g.Sum(q => q.OrderTotal) };

            if (item.Count() == 0)
            {
                return new OrderAverageReportLine() { CountOrders = 0, SumOrders = 0 };
            }
            return new OrderAverageReportLine() { CountOrders = item.ToList().Sum(q => q.count), SumOrders = item.ToList().Sum(q => q.ordertotal) };
        }

        public virtual OrderAverageReportLineSummary OrderAverageReport(OrderStatus os)
        {
            var item = new OrderAverageReportLineSummary();
            item.OrderStatus = os;
            var orderStatuses = new List<int>() { (int)os };
            DateTime nowDt = DateTime.Now;
            DateTime? startTime1 = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day);
            var todayResult = GetOrderAverageReportLine(orderStatuses, startTimeUtc: startTime1);
            item.SumTodayOrders = todayResult.SumOrders;
            item.CountTodayOrders = todayResult.CountOrders;

            DayOfWeek fdow = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
            DateTime today = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day);
            DateTime? startTime2 = today.AddDays(-(today.DayOfWeek - fdow));
            var weekResult = GetOrderAverageReportLine(orderStatuses, startTimeUtc: startTime2);
            item.SumThisWeekOrders = weekResult.SumOrders;
            item.CountThisWeekOrders = weekResult.CountOrders;

            DateTime? startTime3 = new DateTime(nowDt.Year, nowDt.Month, 1);
            var monthResult = GetOrderAverageReportLine(orderStatuses, startTimeUtc: startTime3);
            item.SumThisMonthOrders = monthResult.SumOrders;
            item.CountThisMonthOrders = monthResult.CountOrders;

            DateTime? startTime4 = new DateTime(nowDt.Year, 1, 1);
            var yearResult = GetOrderAverageReportLine(orderStatuses, startTimeUtc: startTime4);
            item.SumThisYearOrders = yearResult.SumOrders;
            item.CountThisYearOrders = yearResult.CountOrders;

            var allTimeResult = GetOrderAverageReportLine(orderStatuses);
            item.SumAllTimeOrders = allTimeResult.SumOrders;
            item.CountAllTimeOrders = allTimeResult.CountOrders;

            return item;
        }

        public virtual IList<BestsellersReportLine> BestSellersReport(int? categoryId = null, int brandId = 0, DateTime? startTime = null,
            DateTime? endTime = null, OrderStatus? os = null, PaymentStatus? ps = null, ShippingStatus? ss = null,
            int recordsToReturn = 5, int orderBy = 1, int groupBy = 1, bool showHidden = false)
        {
            int? orderStatusId = null;
            if (os.HasValue)
                orderStatusId = (int)os.Value;

            int? paymentStatusId = null;
            if (ps.HasValue)
                paymentStatusId = (int)ps.Value;

            int? shippingStatusId = null;
            if (ss.HasValue)
                shippingStatusId = (int)ss.Value;


            var query1 = from opv in _opvRepository.Table
                         join o in _orderRepository.Table on opv.OrderId equals o.Id
                         join p in _goodsRepository.Table on opv.GoodsId equals p.Id
                         where (!startTime.HasValue || startTime.Value <= o.CreateTime) &&
                         (!endTime.HasValue || endTime.Value >= o.CreateTime) &&
                         (!(categoryId.HasValue && categoryId.Value > 0) || categoryId.Value == p.CategoryId) &&
                         (!(brandId > 0) || brandId == p.BrandId) &&
                         (!orderStatusId.HasValue || orderStatusId == o.OrderStatusId) &&
                         (!paymentStatusId.HasValue || paymentStatusId == o.PaymentStatusId) &&
                         (!shippingStatusId.HasValue || shippingStatusId == o.ShippingStatusId) &&
                         (!o.Deleted) &&
                         (!p.Deleted) &&
                         (showHidden || p.Published)
                         select opv;

            var query2 = groupBy == 1 ?
                from opv in query1
                group opv by opv.GoodsId into g
                select new
                {
                    EntityId = g.Key,
                    TotalAmount = g.Sum(x => x.Price),
                    TotalQuantity = g.Sum(x => x.Quantity),
                }
                :
                from opv in query1
                group opv by opv.GoodsId into g
                select new
                {
                    EntityId = g.Key,
                    TotalAmount = g.Sum(x => x.Price),
                    TotalQuantity = g.Sum(x => x.Quantity),
                }
                ;

            switch (orderBy)
            {
                case 1:
                    {
                        query2 = query2.OrderByDescending(x => x.TotalQuantity);
                    }
                    break;
                case 2:
                    {
                        query2 = query2.OrderByDescending(x => x.TotalAmount);
                    }
                    break;
                default:
                    throw new ArgumentException("Wrong orderBy parameter", "orderBy");
            }

            if (recordsToReturn != 0 && recordsToReturn != int.MaxValue)
                query2 = query2.Take(recordsToReturn);

            var result = query2.ToList().Select(x =>
            {
                var reportLine = new BestsellersReportLine()
                {
                    EntityId = x.EntityId,
                    TotalAmount = x.TotalAmount,
                    TotalQuantity = x.TotalQuantity
                };
                return reportLine;
            }).ToList();

            return result;
        }

        public virtual IList<Goods> GetGoodssAlsoPurchasedById(int goodsId,
            int recordsToReturn = 5, bool showHidden = false)
        {
            if (goodsId == 0)
                throw new ArgumentException("Goods ID is not specified");

            var query1 = (from opv in _opvRepository.Table
                          join p in _goodsRepository.Table on opv.GoodsId equals p.Id
                          where p.Id == goodsId
                          select opv.OrderId).Distinct();

            var query2 = from opv in _opvRepository.Table
                         join p in _goodsRepository.Table on opv.GoodsId equals p.Id
                         where (query1.Contains(opv.OrderId)) &&
                         (p.Id != goodsId) &&
                         (showHidden || p.Published) &&
                         (!p.Deleted)
                         select new { opv, p };

            var query3 = from opv_p in query2
                         group opv_p by opv_p.p.Id into g
                         select new
                         {
                             GoodsId = g.Key,
                             GoodssPurchased = g.Sum(x => x.opv.Quantity),
                         };
            query3 = query3.OrderByDescending(x => x.GoodssPurchased);

            if (recordsToReturn > 0)
                query3 = query3.Take(recordsToReturn);

            var report = query3.ToList();
            var list = new List<Goods>();
            foreach (var reportLine in report)
                list.Add(_goodsService.GetGoodsById(reportLine.GoodsId));

            return list;
        }

        public virtual IPagedList<Goods> GoodssNeverSold(DateTime? startTime,
            DateTime? endTime, int pageIndex, int pageSize, bool showHidden = false)
        {
            var query1 = (from opv in _opvRepository.Table
                          join o in _orderRepository.Table on opv.OrderId equals o.Id
                          where (!startTime.HasValue || startTime.Value <= o.CreateTime) &&
                                (!endTime.HasValue || endTime.Value >= o.CreateTime) &&
                                (!o.Deleted)
                          select opv.GoodsId).Distinct();

            var query2 = from p in _goodsRepository.Table
                         where (!query1.Contains(p.Id)) &&
                               (!p.Deleted) &&
                               (showHidden || p.Published)
                         select p;

            var query3 = query2.OrderBy(x => x.Id);

            var list = new PagedList<Goods>(query3, pageIndex, pageSize);
            return list;
        }

        public virtual decimal ProfitReport(List<int> osIds = null, List<int> psIds = null, List<int> ssIds = null,
            DateTime? startTimeUtc = null, DateTime? endTimeUtc = null)
        {
            var orders = _orderRepository.Table;
            if (osIds != null && osIds.Any())
                orders = orders.Where(o => osIds.Contains(o.OrderStatusId));
            if (psIds != null && psIds.Any())
                orders = orders.Where(o => psIds.Contains(o.PaymentStatusId));
            if (ssIds != null && ssIds.Any())
                orders = orders.Where(o => ssIds.Contains(o.ShippingStatusId));

            var query = from opv in _opvRepository.Table
                        join o in orders on opv.OrderId equals o.Id
                        join p in _goodsRepository.Table on opv.GoodsId equals p.Id
                        where (!startTimeUtc.HasValue || startTimeUtc.Value <= o.CreateTime) &&
                              (!endTimeUtc.HasValue || endTimeUtc.Value >= o.CreateTime) &&
                              (!o.Deleted) &&
                              (!p.Deleted)
                        select new { opv, p };

            var profit = Convert.ToDecimal(query.Sum(o => (decimal?)o.opv.Price));
            return profit;
        }

        #endregion

    }
}
